#ifndef CAMERA_H
#define CAMERA_H

#include <QtCore>

#include "vecmath.h"
#include "ray.h"
#include "element.h"

class Camera
{
public:
    Camera();
    Camera(Vec3f position);
    Camera(Vec3f position, Vec3f direction, float angle = 60.0f);
    
    float getAngle() const { return angle; }
    void setAngle(float d) { angle = d; }
    
    float getVerticalAngle() const;

    void getPosition(Vec3f out) { SetVec3f(out, position); }
    void setPosition(Vec3f pos) { SetVec3f(position, pos); }
    void getDirection(Vec3f out);
    void setDirection(Vec3f dir) { pitchYawDirty = true; SetVec3f(direction, dir); }
	
    float getPitch();
    void setPitch(float d) { directionDirty = true; pitch = d; while (pitch < 0) pitch += 360; while (pitch >= 360) pitch -= 360; }
	bool noPitch() { return (-0.1f < getPitch() && getPitch() < 0.1f); };
    float getYaw();
    void setYaw(float d) { directionDirty = true; yaw = d; while (yaw < 0) yaw += 360; while (yaw >= 360) yaw -= 360; }
	bool noYaw() { return (-0.1f < getYaw() && getYaw() < 0.1f); }
    
    float getWidth() const { return viewSize[0]; }
    float getHeight() const { return viewSize[1]; }
    void getViewSize(Vec2f out) { SetVec2f(out, viewSize); }
    void setViewSize(Vec2f size) { SetVec2f(viewSize, size); }
    void setViewSize(float width, float height) { SetVec2f(viewSize, width, height); }
    
	float getFrustumRight();
	float getFrustumLeft();
	float getFrustumTop();
	float getFrustumBottom();
	float getFrustumNear();
	float getFrustumFar();
	
    void cameraToWorldMat4f(Mat4f out);
    void cameraToWorldDirectionMat4f(Mat4f out);
    
    void screenToWorldMat4f(Mat4f out);
    void worldToScreenMat4f(Mat4f out);
    
    void worldToNormalizedSpaceMat4f(Mat4f out);
    void normalizedSpaceToWorldMat4f(Mat4f out);
	
	void screenToNormalizedSpaceMat4f(Mat4f out);
	void normalizedSpaceToScreenMat4f(Mat4f out);
    
    void tangent(Vec3f out);
    void normal(Vec3f out);
    void binormal(Vec3f out);

	void setRightClipPlane(float x);
	void setLeftClipPlane(float x);
	void enableClipPlanes();
	void disableClipPlanes();
	void clearClipPlanes();
    
    void setModelView();
    void setProjection();
    void setBillBoard(); // To be set near the original model data (in code: modelview, model positioning in world, billboard, model center definition)
    
    void pointAt(Vec3f target);
    void moveForward(float d);
    void moveUpward(float d);
    void moveSideways(float d);
	void moveX(float d);
	void moveY(float d);
	void moveZ(float d);
    
    void getWorldPoint(Vec3f out, Vec2f screenPoint, float depth);
    void getRightPlaneComponents(Vec4f out, Vec3f positionScale, float pitchScale = 1.0f, float yawScale = 1.0f);
    void getLeftPlaneComponents(Vec4f out, Vec3f positionScale, float pitchScale = 1.0f, float yawScale = 1.0f);
    void getTopPlaneComponents(Vec4f out, Vec3f positionScale, float pitchScale = 1.0f, float yawScale = 1.0f);
    void getBottomPlaneComponents(Vec4f out, Vec3f positionScale, float pitchScale = 1.0f, float yawScale = 1.0f);
    
    Ray getNormalizedSpacePickRay(Vec3f start, Vec3f end);
    Ray getPickRay(float screenX, float screenY, bool flip = false);
    
private:
    void derivePitchYaw();
    
protected:
    float angle;
    
    Vec3f position, direction;
    float pitch, yaw;
    Vec2f viewSize;
	
	bool rightClipDefined;
	bool leftClipDefined;
	
	float rightClipPlane;
	float leftClipPlane;
	
	float clipPlanesEnabled;
    
private:
    bool directionDirty;
    bool pitchYawDirty;
};


class CameraController : public QObject
{
	Q_OBJECT
	
public:
	CameraController();
	
	Camera& getCamera() { return camera; }
	
	void getScaling(Vec3f out) { SetVec3f(out, scaling); }
	void setScaling(Vec3f in) { SetVec3f(scaling, in); }
	
	void pushAnimation(QTimeLine* anim);
	void popAnimation(QTimeLine* anim);
	
public slots:
	void setScaling(float horizontal, float vertical);
	
	void move(float horizontalDir, float verticalDir = 0.0f, float frontDir = 0.0f);
	void moveLeft(float step = 1.0f);
	void moveRight(float step = 1.0f);
	
	void moveTo(Vec3f pos, bool animate = true);
	void moveToHorizontalPos(float horizontalPos, bool animate = true) { Vec3f vec; SetVec3f(vec, cameraUnpanPosition); vec[0] = horizontalPos; moveTo(vec, animate); }
	void panTo(float angle, float animatie = true);
	
	void moveToTrace(Vec3f tracePos, bool animate = true);
	void moveToTraceHorizontalPos(float horizontalPos, bool animate = true) { Vec3f vec; SetVec3f(vec, cameraTracePosition); vec[0] = horizontalPos; moveToTrace(vec, animate); }
	
	void moveTo(QList<Element*> elements, BoundingBox boundingBox, bool animate = true, bool useVerticalDisplacement = true, bool useScaleChange = false, bool useHorizondalDisplacement = true);

signals:
	void render();

protected:
	void updateCameraUnpanPosition();
	void updateCameraTracePosition();
	void getCameraWorldPosition(Vec3f out);
	void unpanCamera();
	void panCamera();
	
protected:
	Camera camera;
	Vec3f scaling;
	
	float panAngle;
	Vec3f cameraUnpanPosition;
	Vec3f cameraTracePosition;
};


class Animation : public QTimeLine
{
	Q_OBJECT
	
public:
	Animation(CameraController* cameraController);
	
protected slots:
	virtual void setNormalizedValue(qreal value);
	void setFinished();
	
protected:
	CameraController* cameraController;
};


class MoveAnimation : public Animation
{
	Q_OBJECT
	
public:
	MoveAnimation(CameraController* cameraController);
	void setPositions(Vec3f begin, Vec3f end);
	void setEndPosition(Vec3f end);

protected slots:
	virtual void setNormalizedValue(qreal value);

private:
	Vec3f begin;
	Vec3f end;
};


class PanAnimation : public Animation
{
	Q_OBJECT
	
public:
	PanAnimation(CameraController* cameraController);
	void setClockwise(bool clockwise);
	void setAngles(float begin, float end, bool shortest = true);
	void setEndAngle(float end, bool shortest = true);
	
protected slots:
	virtual void setNormalizedValue(qreal value);

private:
	float begin;
	float end;
	
	bool clockwise;
};

#endif // CAMERA_H
